using Godot;
using System;
using System.Collections.Generic;

using CurrentGame.NPC;
using CurrentGame.Placements;
using CurrentGame.Levels;
using CurrentGame.GUI.PlaceGrid;

public partial class Player : CharacterBody2D
{
	[Signal]
	public delegate void HealthPointChangedEventHandler(int hp);

	[ExportGroup("Movement controls")]
	[Export]
	public float Speed = 300.0f;
	[Export]
	public float FractionFactor { get; set; } = 0.5f;
	[Export]
	public float EspSpeed { get; set; } = 0.5f;

	[ExportGroup("Jump controls")]
	[Export]
	public float JumpVelocity = -400.0f;
	[Export]
	public float UnderShadeJumpBoost { get; set; } = 1.2f;
	[Export]
	public float JumpBoostLightScale { get; set; } = 0.2f;
	[Export]
	public float ScaleInterpolationWeight
	{
		get => _scaleInterpolationWeight;
		set
		{
			if (_lightStack != null)
			{
				_scaleInterpolationWeight = value;
				_lightStack.SetInterpolationWeight(value);
			}
		}
	}
	private float _scaleInterpolationWeight = 0.2f;

	[ExportGroup("Light damage")]
	[Export]
	public float DamageKnockBackVelocity { get; set; } = -10.0f;
	[Export]
	public float KnockBackInterpolation { get; set; } = 0.1f;
	[Export]
	public float KnockBackSpeedDeadZone { get; set; } = 0.5f;

	[ExportGroup("Camera management")]
	[Export]
	public CinematicCamera Camera { get; set; }
	[Export]
	public Vector2 UpDownLookOffset { get; set; } = Vector2.Zero;

	[ExportGroup("Player properties")]
	[Export]
	public int HealthPoint
	{
		get
		{
			return _hpManager == null ? -1 : _hpManager.CurrentHP;
		}
		set
		{
			if (_hpManager != null)
			{
				_hpManager.CurrentHP = value;
				_lightRangeHint.SetRangeLevel(value);
				EmitSignal(SignalName.HealthPointChanged, value);
			}
		}
	}
	[Export]
	public bool AttackEnabled { get; set; } = false;

	[ExportGroup("Place mode")]
	[Export]
	public bool PlaceModeAvailable { get; set; } = false;

    [ExportGroup("Node references")]
	[Export]
	public PlayerAnimationTree AnimationFlags { get; set; }
	[Export]
	public Sprite2D PlayerSprite { get; set; }
	[Export]
	public TimePeriodManager TimePeriod { get; set; }
    [Export]
    public LightActionHint LightActionHint { get; set; }
	[Export]
	public SceneSwitch SceneLevelSwitch { get; set; }

    // Physics variables
    private bool jumping = false;
	private bool falling = false;
	private float Facing
	{
		get => _facing;
		set
		{
			if ((_facing > 0 && value < 0) || (_facing < 0 && value > 0))
			{
				_facing = value;
				PlayerSprite.FlipH = value < 0;
			}
		}
	}
	private float _facing = 1.0f;

	// Long press / release control
	private Dictionary<string, bool> _pressedStates = [];
	private Vector2 _camOffsetBackup;

	// Input management
	private Vector2 _camOffset;
	private int _inputEnabled = 0;

	// Shade maintain
	private Dictionary<Guid, ShadeCharacter> _currentShades = [], _footShades = [];
	private bool _boostJumping = false, _steppingOffShade = false;
	private bool _stepOffPressed = false;

	// In light process
	private Area2D _generalAreaDetect;
	private int _inLightCount = 0;
	private bool _inLight = false, _inKnockBack = false;
	private float _knockBackSpeed = 0.0f;

	// Child node reference
	private HpManager _hpManager;
	private PlayerLightStack _lightStack;
	private PlayerParticleEmitter _particleEmitter;

	// Place mode management
	private bool _enablePlaceMode = false, _skippedPlaceMode = false;
	private PlaceModeGrid _placeModeGrid;

	// Action Range Hint
	private LightRangeHint _lightRangeHint;
	private LightBurstRange _lightBurstRange;
	private bool _attackAvailable = true;

	// LightMem
	private LightMemory _currentMem;

	// progress manager
	private GameProgressManager _progressManager;

	// Switch Scene
	private string _nextSceneName;
	private float _switchInterval;
	private bool _switchScene = false;


    public override void _Ready()
    {
		TimePeriod.RegisterTimePeriod("player_move_up", 0.5);
		_pressedStates.Add("player_move_up", false);
        TimePeriod.RegisterTimePeriod("player_move_down", 0.5);
        _pressedStates.Add("player_move_down", false);

		_camOffsetBackup = Camera.FocusOffset;
		Camera.ShowMask = false;
		_hpManager = GetNode<HpManager>("HPManager");
		_lightStack = GetNode<PlayerLightStack>("Light");
		_generalAreaDetect = GetNode<Area2D>("AreaDetector");
		_lightRangeHint = GetNode<LightRangeHint>("RangeHint");
		_lightBurstRange = GetNode<LightBurstRange>("LightDetect");
		_particleEmitter = GetNode<PlayerParticleEmitter>("ParticleEmitter");

		var map = GetNode<NodeReferenceMap>(Constants.NodeRefMapPath);
        _placeModeGrid = map.PlacementModeGrid;
		_progressManager = map.ProgressManager;
    }

	public override void _PhysicsProcess(double delta)
	{
		Vector2 velocity = Velocity, currentGravity = GetGravity();
		bool currentOnFloor = IsOnFloor();
		_stepOffPressed = false;

        // Get the input direction and handle the movement/deceleration.
        Vector2 direction = Vector2.Zero;
        if (_inputEnabled <= 0)
            direction = Input.GetVector("player_move_left", "player_move_right", "player_move_up", "player_move_down");

        if (!currentOnFloor)
		{
			// We're in air

			// Add the gravity.
			velocity += currentGravity * (float)delta;

			// If velocity face down, then we're falling
			if (velocity.Y > 0 && !falling)
			{
				falling = true;
				AnimationFlags.IsFalling = true;
			}
		}
		else
		{
			// We're on ground

			// No input, player is idle
			if (direction == Vector2.Zero)
			{
				if (_inputEnabled <= 0 && Input.IsActionJustPressed("switch_place_mode"))
				{
					if (PlaceModeAvailable)
					{
						if (_enablePlaceMode) DisablePlaceMode();
						else EnablePlaceMode();
					}
				}
				if (_inputEnabled <= 0 && Input.IsActionJustPressed("player_attack"))
				{
					if (AttackEnabled && _attackAvailable)
					{
						TriggerBurstAction();
					}
				}
			}

			if (jumping)
			{
				// We just touch the ground after on jump action
				jumping = false;
				AnimationFlags.IsJumping = false;
				
				// Check boost jumping state
				if (_boostJumping)
				{
					_lightStack.SetStackScale(1.0f);
					_boostJumping = false;
				}
			}

			if (falling)
			{
				// We just fall to the ground
				falling = false;
				AnimationFlags.IsFalling = false;

                // Check step off state
                if (_steppingOffShade)
				{
					// Stop stepping off
					_steppingOffShade = false;
					// Reset light stack scale
					_lightStack.SetStackScale(1.0f);
					// Notify all shades under feet to enable collision
					foreach (var item in _footShades.Values) item.EnableHeadCollision();
				}
            }
		}

		// Handle Jump.
		bool canJump = currentOnFloor;

        if (_inputEnabled <= 0 && Input.IsActionJustPressed("player_jump") && canJump)
		{
			if (_currentShades.Count <= 0 || direction.X != 0.0f)
			{
				// Normal jump
				velocity.Y = JumpVelocity;
			}
			else
			{
				// Boosted jump
				velocity.Y = JumpVelocity * UnderShadeJumpBoost;

				// Notify shade to cancel collision
				foreach (var item in _currentShades.Values)
					item.DisableHeadCollision();
				_boostJumping = true;

				_lightStack.SetStackScale(JumpBoostLightScale);
			}

            jumping = true;
			AnimationFlags.IsJumping = true;
		}

		// Reset X axis input if we are boost jumping
		if (_boostJumping) direction.X = 0;
		
		if (direction.X != 0)
		{
			velocity.X = direction.X * Speed;
			Facing = direction.X;
			AnimationFlags.IsWalking = true;
		}
		else
		{
			// No X axis input

			// Push player out of light
			if (_inKnockBack)
			{
				// Decrease speed
				if (!_inLight)
					_knockBackSpeed = Mathf.Lerp(_knockBackSpeed, 0.0f, KnockBackInterpolation);
				// else: Maintain speed if still in light
				if (Mathf.Abs(_knockBackSpeed) < KnockBackSpeedDeadZone)
				{
					// End knock back process if speed is small enough
					_knockBackSpeed = 0.0f;
					_inKnockBack = false;
					// Reset count
					EnableInputCheck();
				}
				velocity.X = _knockBackSpeed * _facing;
			}

			// Process velocity
			if (Mathf.Abs(velocity.X) < EspSpeed)
			{
				//  Reset velocity if it's small enough
				velocity.X = 0;
				AnimationFlags.IsWalking = false;
			}
			else
			{
				// Weight calculation here is not quite correct.
				// Use CurrenGame.Utils.Interpolation LerpInterpolation.
				velocity.X = Mathf.Lerp(velocity.X, 0, FractionFactor * (float)delta);
			}
		}

		MaintainLongPressAction("player_move_up");
		MaintainLongPressAction("player_move_down");

        // leave shade head - Updated in MaintainLongPressAction
        if (_stepOffPressed && _footShades.Count > 0)
		{
			// Start stepping off
			_steppingOffShade = true;
			// Set scale of light stack
			_lightStack.SetStackScale(JumpBoostLightScale);
			// Notice shades under foot to cancel collision
			foreach(var item in _footShades.Values) item.DisableHeadCollision();
		}

		Velocity = velocity;
		MoveAndSlide();

		QueueRedraw();
	}

	// ====================
	// private functions

	// Set camera properties
	private void ApplyCameraAreaConfig(CameraAreaConfig config)
	{
		Camera.PushCameraAreaConfig(config);
	}

	private void RemoveCameraAreaConfig(CameraAreaConfig config)
	{
		Camera.PopCameraAreaConfig();
	}

	// Long press management
	private void MaintainLongPressAction(string actionName)
	{
		if (_inputEnabled <= 0 && Input.IsActionJustPressed(actionName))
		{
			TimePeriod.StartTimePeriod(actionName);
		}
		if (Input.IsActionJustReleased(actionName))
		{
			if (_pressedStates[actionName])
			{
				_pressedStates[actionName] = false;
				Camera.FocusOffset = _camOffsetBackup;
			}
            else if (actionName == "player_move_down")
            {
				// special case for stepping off shade
				_stepOffPressed = true;
            }
            TimePeriod.InterruptTimePeriod(actionName);
		}
	}

	// Shade collisions
	private void EnterShadeArea(ShadeBodyArea shade)
	{
		_currentShades.Add(shade.BindShade.ShadeID, shade.BindShade);
	}

	private void LeaveShadeArea(ShadeBodyArea shade)
	{
		shade.BindShade.EnableHeadCollision();
		_currentShades.Remove(shade.BindShade.ShadeID);
	}

	// Light burst action
	private void TriggerBurstAction()
	{
		LightActionHint.StartAnimation(HealthPoint);
		_lightBurstRange.BurstAction();
		HealthPoint -= 1;
		// Set attack cool down
		_attackAvailable = false;
	}

	// Light Dialogue
	private void ConsumeLightMem()
	{
		if (_currentMem != null)
		{
			HealthPoint = 3;
			_currentMem.HideMem();
			_currentMem = null;
		}
	}

	// Switch Scene
	private void SwitchLevel()
    {
        _inLightCount = 0;
		_inLight = false;
		_inKnockBack = false;
		_knockBackSpeed = 0.0f;

		GlobalPosition = new Vector2(0.0f, 0.0f);
        ResetPhysicsInterpolation();
        Camera.GlobalPosition = new Vector2(0.0f, 0.0f);
		Camera.ResetInterpolation();

        SceneLevelSwitch.ArrangeStartScene(_switchInterval);
        SceneLevelSwitch.SwitchToScene(_nextSceneName);
    }

    // =================
    // Public interfaces

	// Place shade mode
	// Made it PUBLIC so that level scripts can control it.
	public void EnablePlaceMode()
	{
		_enablePlaceMode = true;
		_placeModeGrid.EnablePlaceMode();
		_lightRangeHint.ShowRangeHint();
	}

	public void DisablePlaceMode()
	{
		_enablePlaceMode = false;
		_placeModeGrid.DisablePlaceMode();
		_lightRangeHint.HideRangeHint();
	}

	// Light area
    public void EnterLightArea(LightPlayerDetect light)
	{
        _inLightCount += 1;
		if (_inLightCount > 0 && !_inLight)
		{
			// We're now in light
			_inLight = true;
			// Enable knock back
			_inKnockBack = true;
			_knockBackSpeed = DamageKnockBackVelocity;
			// Deal damage to player
			HealthPoint -= 1;
			SkipInputCheck();
		}
    }

    public void LeaveLightArea(LightPlayerDetect light)
	{
        _inLightCount -= 1;
		if (_inLightCount <= 0 && _inLight)
		{
			_inLight = false;
		}
	}
	
	// Health points
	public void SetHealthPoint(int point)
	{
		_hpManager.SetHP(point);
		_lightRangeHint.SetRangeLevel(point);
		EmitSignal(SignalName.HealthPointChanged, point);
	}
	
	// Input switch
	public void ResetSkippedPlaceMode()
	{
		_skippedPlaceMode = false;
	}

	public void SkipInputCheck()
	{
		_inputEnabled += 1;
		if (_enablePlaceMode)
		{
			_skippedPlaceMode = true;
			DisablePlaceMode();
		}
	}

	public void EnableInputCheck(bool reset = false)
	{
		_inputEnabled -= 1;
		if (reset) _inputEnabled = 0;
		if (_skippedPlaceMode)
		{
			_skippedPlaceMode = false;
			EnablePlaceMode();
		}
	}

	// Callback of dialogue
	public void HandleCustomEvent(string eventName, Godot.Collections.Dictionary<string, Variant> args)
	{
		if (eventName == "health")
		{
            if (args.TryGetValue("value", out Variant variant))
            {
				HealthPoint += variant.AsInt32();
            }
        }
		else if (eventName == "light_mem")
		{
			ConsumeLightMem();
		}
	}

	// Use by demage_light to detect player by rayCast
	public Rid GetAreaDetectorRID()
	{
		return _generalAreaDetect.GetRid();
	}

	// Record current light_mem so that we can destroy it
	public void NotifyLightMemoryUpdate(LightMemory light = null)
	{
		_currentMem = light;
	}

	// Level controls
	public void RestartThisLevel()
	{
		StartTargetLevel(SceneLevelSwitch.CurrentSceneName);
	}

	public void StartTargetLevel(string sceneName, float interval = 2.0f)
	{
		_nextSceneName = sceneName;
		_switchInterval	= interval;
        Camera.ShowMask = true;
        _switchScene = true;
    }

	// =============
	// Godot signals
	private void Signal_AreaEnter(Area2D area)
	{
        if (area is CameraAreaConfig cameraConfig)
			ApplyCameraAreaConfig(cameraConfig);
		else if (area is DialogueConfig dialogueConfig)
			dialogueConfig.PrepareDialogue();
		else if (area is ShadeBodyArea bodyArea)
		{
			bodyArea.PlayerEnter(this);
			EnterShadeArea(bodyArea);
		}
		else if (area is LevelTeleport teleport)
		{
			teleport.Teleport();
		}
	}

	private void Signal_AreaLeave(Area2D area)
	{
        if (area is CameraAreaConfig cameraConfig)
			RemoveCameraAreaConfig(cameraConfig);
		else if (area is DialogueConfig dialogueConfig)
			dialogueConfig.CancelDialogue();
		else if (area is ShadeBodyArea bodyArea)
		{
			bodyArea.PlayerLeave(this);
			LeaveShadeArea(bodyArea);
		}
    }

	private void Signal_LongPressTimeout(string keyname)
	{
		_pressedStates[keyname] = true;
		_camOffsetBackup = Camera.FocusOffset;
		if (keyname == "player_move_up")
		{
			Camera.FocusOffset = _camOffsetBackup - UpDownLookOffset;
		}
		else if (keyname == "player_move_down")
		{
            Camera.FocusOffset = _camOffsetBackup + UpDownLookOffset;
        }
	}

	private void Signal_FootAreaEnter(Area2D area)
	{
		if (area is ShadeBodyArea bodyArea)
		{
			_footShades.Add(bodyArea.BindShade.ShadeID, bodyArea.BindShade);
		}
	}

	private void Signal_FootAreaLeave(Area2D area)
	{
		if (area is ShadeBodyArea bodyArea)
		{
			bodyArea.BindShade.EnableHeadCollision();
			_footShades.Remove(bodyArea.BindShade.ShadeID);
		}
	}

	private void Signal_BurstAnimationFinished()
	{
		_attackAvailable = true;
	}

    private void Signal_CameraAnimationFinished(bool show)
    {
        if (_switchScene && show)
        {
            _switchScene = false;
			SwitchLevel();
        }
    }

	private void Signal_PlayerDead()
	{
		if (_progressManager.ExtremeMode) StartTargetLevel("start_level");
		else RestartThisLevel();
	}
}
